package com.newshub.logging.aspect;

import com.newshub.logging.annotation.BusinessLog;
import com.newshub.logging.annotation.LogLevel;
import com.newshub.logging.context.LogContext;
import com.newshub.logging.config.LoggingProperties;
import com.newshub.logging.core.SystemLogger;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import jakarta.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.UUID;

/**
 * 日志切面
 * 拦截带有日志注解的方法调用
 */
@Slf4j
@Aspect
@Component
@ConditionalOnProperty(name = "logging.enabled", havingValue = "true", matchIfMissing = true)
public class LoggingAspect {
    
    @Autowired
    private SystemLogger systemLogger;
    
    @Autowired
    private LoggingProperties loggingProperties;
    
    /**
     * 业务日志切面
     * 拦截带有 @BusinessLog 注解的方法
     */
    @Around("@annotation(businessLog)")
    public Object aroundBusinessLog(ProceedingJoinPoint joinPoint, BusinessLog businessLog) throws Throwable {
        // 检查业务日志是否启用
        if (!loggingProperties.isBusinessLogEnabled()) {
            // 如果业务日志被禁用，直接执行原方法，不记录日志
            return joinPoint.proceed();
        }
        
        long startTime = System.currentTimeMillis();
        String requestId = generateRequestId();
        
        // 构建日志上下文
        LogContext context = buildLogContext(joinPoint, businessLog, requestId);
        
        try {
            // 记录方法开始
            systemLogger.info("方法开始执行", context);
            
            // 执行原方法
            Object result = joinPoint.proceed();
            
            // 计算执行时间
            long executionTime = System.currentTimeMillis() - startTime;
            context.setExecutionTime(executionTime);
            context.setMethodResult(result);
            
            // 记录方法成功
            systemLogger.info("方法执行成功", context);
            
            return result;
            
        } catch (Throwable throwable) {
            // 计算执行时间
            long executionTime = System.currentTimeMillis() - startTime;
            context.setExecutionTime(executionTime);
            context.setException(throwable);
            
            // 记录方法异常
            systemLogger.error("方法执行异常", context, throwable);
            
            // 重新抛出异常
            throw throwable;
        }
    }
    
    /**
     * 构建日志上下文
     */
    private LogContext buildLogContext(ProceedingJoinPoint joinPoint, BusinessLog businessLog, String requestId) {
        // 自动推断模块名称（从类名）
        String module = businessLog.module();
        if (module == null || module.trim().isEmpty()) {
            String className = joinPoint.getTarget().getClass().getSimpleName();
            if (className.endsWith("Controller")) {
                module = className.substring(0, className.length() - 10); // 去掉"Controller"后缀
            } else if (className.endsWith("Service")) {
                module = className.substring(0, className.length() - 7); // 去掉"Service"后缀
            } else {
                module = className;
            }
        }
        
        // 为空的描述提供默认值
        String description = businessLog.description();
        if (description == null || description.trim().isEmpty()) {
            description = "执行方法: " + joinPoint.getSignature().getName();
        }
        
        LogContext.LogContextBuilder builder = LogContext.builder()
                .module(module)
                .description(description)
                .level(businessLog.level())
                .timestamp(LocalDateTime.now())
                .requestId(requestId)
                .methodName(joinPoint.getSignature().getName())
                .className(joinPoint.getTarget().getClass().getSimpleName())
                .methodParams(joinPoint.getArgs())
                .threadId(Thread.currentThread().getId())
                .threadName(Thread.currentThread().getName());
        
        // 获取HTTP请求信息
        try {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                builder.ipAddress(getClientIpAddress(request))
                       .requestUrl(request.getRequestURL().toString())
                       .requestMethod(request.getMethod());
            }
        } catch (Exception e) {
            log.debug("无法获取HTTP请求信息", e);
        }
        
        return builder.build();
    }
    
    /**
     * 生成请求ID
     */
    private String generateRequestId() {
        return UUID.randomUUID().toString().replace("-", "");
    }
    
    /**
     * 获取客户端IP地址
     */
    private String getClientIpAddress(HttpServletRequest request) {
        String xForwardedFor = request.getHeader("X-Forwarded-For");
        if (xForwardedFor != null && !xForwardedFor.isEmpty() && !"unknown".equalsIgnoreCase(xForwardedFor)) {
            return xForwardedFor.split(",")[0];
        }
        
        String xRealIp = request.getHeader("X-Real-IP");
        if (xRealIp != null && !xRealIp.isEmpty() && !"unknown".equalsIgnoreCase(xRealIp)) {
            return xRealIp;
        }
        
        return request.getRemoteAddr();
    }
    
    /**
     * 手动记录日志的方法（保留用于测试）
     */
    public void logBusinessOperation(String module, String description, String message) {
        LogContext context = LogContext.builder()
                .module(module)
                .description(description)
                .level(LogLevel.INFO)
                .timestamp(LocalDateTime.now())
                .requestId(generateRequestId())
                .methodName("logBusinessOperation")
                .className("LoggingAspect")
                .threadId(Thread.currentThread().getId())
                .threadName(Thread.currentThread().getName())
                .build();
        
        systemLogger.info(message, context);
    }
}
